/* drivers/leds/leds-s3c24xx.c
 *
 * (c) 2006 Simtec Electronics
 *	http://armlinux.simtec.co.uk/
 *	Ben Dooks <ben@simtec.co.uk>
 *
 * s5pv210 - LEDs GPIO driver
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/slab.h>   // kzalloc 函数库

#include <mach/hardware.h>



#include <mach/regs-gpio.h>   // gpio-bank 中的 S5PV210_GPA0_BASE 来自 regs-gpio
#include <mach/gpio-bank.h>  // 静态映射 S5PV210_GPJ0CON 宏 头文件 

#include <linux/platform_device.h> // platform_driver

// #include <mach/leds-gpio.h>
// kernel\arch\arm\mach-s5pv210\include\mach\gpio.h
#include <mach/gpio.h>   // gpiolib库使用 


//********************************************

// platform_leds_x210_4.c 没有编译到内核，所以自己定义一个，没有用led-gpio.h

#define s5pv210_LEDF_ACTLOW	(1<<0)		/* LED is on when GPIO low */
#define s5pv210_LEDF_TRISTATE	(1<<1)		/* tristate to turn off */

struct s5pv210_led_platdata {
	unsigned int		 gpio;
	unsigned int		 flags;

	char			*name;
	char			*def_trigger;
};
// ********************************************


/* our context */
struct s5pv210_gpio_led {
	struct led_classdev		 cdev;
	struct  s5pv210_led_platdata	*pdata;
};


static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
	return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
	return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}


static void s5pv210_led_set(struct led_classdev *led_cdev,  enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led_set1   %s  -  %d\n", led_cdev->name, led_cdev->brightness);
	//通过led_classdev 找到->s5pv210_gpio_led
	struct s5pv210_gpio_led *p = to_gpio(led_cdev);
  printk("p=%p",p);
//	struct s5pv210_led_platdata *pd = p->pdata;

	printk(KERN_INFO "s5pv210_led_set2\n");
  if(p == NULL){
    printk(KERN_INFO "p == NULL\n");
   // return ;
  }
  if(p->pdata == NULL){
    printk(KERN_INFO "p->pdata == NULL\n");
 
  }
  struct led_classdev  led_classdev1 = p->cdev;
  printk(KERN_INFO "led_classdev = %s -  %d \n",led_classdev1.name,led_classdev1.brightness);


 // return ;
	//pGPJ0CON = 0x11111111;

	// 程序员实现具体逻辑操作led ， 操作寄存器 
     if(value == LED_OFF ){
   // 用户给0 ， 灭 
     //   pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));   // 灭
     	  gpio_set_value(p->pdata->gpio, 1);
     }else {
   // 用户给非0 ， 设置亮度
       //  pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));   //亮 
     	   gpio_set_value(p->pdata->gpio, 0);
     }
     printk(KERN_INFO "pd->gpio=%d ",p->pdata->gpio);
}



// static struct led_classdev my_dev  ;




// 有三个设备led1 ,led2 ,led3, 所以该驱动会被调用3次 
static int s5pv210_led_probe(struct platform_device *dev)
{
   
       // led1 
	int ret = -1; 
  struct s5pv210_led_platdata *pdata = dev->dev.platform_data;

  struct s5pv210_gpio_led *led;
	led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
  printk("led=%p",led);


	if (led == NULL) {
		dev_err(&dev->dev, "No memory for device\n");
		return -ENOMEM;
	}
  //没深软用，从s3c24xx  copy过来的
    platform_set_drvdata(dev, led);



    printk(KERN_INFO "s5pv210_led_probe = %s -  %d \n",pdata->name,pdata->gpio);
 
// gpio 编号  label标识 
// 多个led处理：
// 3个led都匹配上platform_leds_x210_4 ，  会走这个函数pro多次，   
// 3个led 走 3次，  报错改成下面
   // if(gpio_request(GPIO_LED1, "led_gpj0_3")) {
   	if(gpio_request(pdata->gpio, pdata->name)) {
           printk(KERN_INFO "gpio_request fail \n");
           return -EINVAL;
    }
    // 设置输出模式，输出1 
    gpio_direction_output(pdata->gpio, 1);



    led->cdev.name = pdata->name;
    led->cdev.brightness =0; 
    led->cdev.brightness_set= s5pv210_led_set;
    led->pdata = pdata;
    ret = led_classdev_register(&dev->dev, &led->cdev); // 这里->优先级高一些 
    if (ret < 0){
         printk(KERN_INFO "led_classdev_register fail \n");
         kfree(led);
         return ret; 
    }
    return 0;
}	

static int s5pv210_led_remove(struct platform_device *dev){
	  struct s5pv210_gpio_led *p = pdev_to_gpio(dev);    
	  led_classdev_unregister(&p->cdev);
	  gpio_free(p->pdata->gpio);
      kfree(p);
      printk(KERN_INFO "s5pv210_led_remove\n");
      return 0;
}
 
// 5.5.7. 
// 单独编译platform_leds_x210_4   insmod以后
// 
// 执行platform_driver_register 函数自动创建： sys/bus/platform/drivers/s5pv210_led  创建文件夹，空的，还没有执行探测函数
//  rmmod platform_leds_x210_4.ko 会删除这个文件夹
//  5.5.8. 
static struct platform_driver s5pv210_led_driver = {
	.probe		= s5pv210_led_probe,
	.remove		= s5pv210_led_remove,
	.driver		= {
		.name		= "s5pv210_led",
		.owner		= THIS_MODULE,
	},
};

static int __init s5pv210_led_init(void)
{
	return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void)
{
	platform_driver_unregister(&s5pv210_led_driver);
}



module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

MODULE_AUTHOR("tony <tony.deng321@gmail.com>");
MODULE_DESCRIPTION("X210 LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("s5pv210-led");
